package com.example.autocode;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.UUID;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.autocode.entity.AutoCode;
import com.example.autocode.entity.AutoCodePart;
import com.example.autocode.entity.AutoCodeResult;
import com.example.autocode.enums.CycleMethodEnum;
import com.example.autocode.enums.PartTypeEnum;
import com.example.autocode.service.IAutoCodePartService;
import com.example.autocode.service.IAutoCodeResultService;
import com.example.autocode.service.IAutoCodeService;
import com.example.autocode.strategy.PartTypeHandler;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author zhangxin
 * @create 2021/12/23 15:48
 */
@Service
@Slf4j
public class AutoCodeUtil {
    public static ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();

    @Autowired
    private IAutoCodeService autoCodeService;

    @Autowired
    private IAutoCodePartService autoCodePartService;

    @Autowired
    private IAutoCodeResultService autoCodeResultService;

    @Autowired
    private PartTypeHandler partTypeHandler;

    private String lastSerialNumber = "";

    @Synchronized
    public String generateCode(String codeNo, String inputCharacter) {
        try {

            log.info("AutoCodeUtil generateCode start ==> codeNo:{}, character:{}", codeNo, inputCharacter);
            // 获取到编码器
            AutoCode autoCode = autoCodeService.getOne(Wrappers.<AutoCode>lambdaQuery().eq(AutoCode::getCode, codeNo));
            Assert.notNull(autoCode, "根据编码[{}]未获取到编码器", codeNo);
            // 获取编码规则
            List<AutoCodePart> autoCodeParts = autoCodePartService.list(Wrappers.<AutoCodePart>lambdaQuery().eq(AutoCodePart::getParentCode, autoCode.getCode()).orderByAsc(AutoCodePart::getPartIndex));
            List<AutoCodePart> collect = autoCodeParts.stream()
                    .filter(part -> PartTypeEnum.PART_TYPE_4.getCode().equals(part.getPartType()))
                    .collect(Collectors.toList());

            Assert.isTrue(collect.size() <= 1, "流水号阶段只能存在一个！");

            StringBuilder sb = new StringBuilder();
            autoCodeParts.forEach(codePart -> {
                // 传入字符特殊处理
                codePart.setInputCharacter(inputCharacter);

                // 分阶段处理
                String partResult = partTypeHandler.choiceExecute(codePart);

                // 如果有流水号阶段，记录一下入库
                if (StringUtils.equals(codePart.getPartType(), PartTypeEnum.PART_TYPE_4.getCode())) {
                    lastSerialNumber = partResult;
                }

                sb.append(partResult);
            });
            Assert.notBlank(sb.toString(), "生成编码为空");

            // 编码补齐
            String strResult = codeSupplement(autoCode, sb);
            // 入库
            // 如果存在流水号阶段，并且循环方式为按传入参数改变来循环，那么需要传入参数作为条件查询
            long count = collect.stream()
                    .filter(part -> "1".equals(part.getIsSerialCycle())
                            && CycleMethodEnum.CYCLE_METHOD_INPUT.getCode().equals(part.getCycleMethod()))
                    .count();

            saveCodeResult(autoCode, strResult, count, inputCharacter);
            return strResult;
        } catch (Exception e) {
            log.info("编码生成失败 异常信息：{}", e);
            throw new RuntimeException("编码生成失败：" + e.getMessage());
        } finally {
            log.info("清空threadlocal");
            threadLocal.remove();
        }
    }

    /**
     * 编码补齐
     *
     * @param autoCode
     * @param sourceCode
     * @return
     */
    private String codeSupplement(AutoCode autoCode, StringBuilder sourceCode) {
        String isPadded = autoCode.getIsPadded();
        if ("1".equals(isPadded)) {
            Integer maxLength = autoCode.getMaxLength();
            String paddedChar = autoCode.getPaddedChar();
            StringBuilder result = new StringBuilder(maxLength);
            long len = maxLength - sourceCode.length();

            // 左补齐
            if ("1".equals(autoCode.getPaddedMethod())) {
                for (; len > 0; len--) {
                    result.append(paddedChar);
                }
                result.append(sourceCode);
            // 右补齐
            } else if ("2".equals(autoCode.getPaddedMethod())) {
                result.append(sourceCode);
                for (; len > 0; len--) {
                    result.append(paddedChar);
                }
            }
            return result.toString();
        }
        return sourceCode.toString();
    }

    /**
     * @param autoCode  编码器编码
     * @param code      生成的编码
     * @param count     阶段为流水号并且循环方式为 按照传入参数循环
     * @param inputChar 输入参数
     */
    private void saveCodeResult(AutoCode autoCode, String code, long count, String inputChar) {
        Boolean flag = AutoCodeUtil.threadLocal.get();
        // 新增编码实例
        if (flag != null && flag) {
            AutoCodeResult result = AutoCodeResult.builder()
                    .id(UUID.randomUUID().toString())
                    .lastResult(code)
                    .parentCode(autoCode.getCode())
                    .generateDate(DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss"))
                    .generateIndex(1)
                    .lastInputChar(inputChar)
                    .lastSerialNumber(Integer.valueOf(lastSerialNumber))
                    .build();
            autoCodeResultService.save(result);
            return;
        }

        /**
         * 修改生成的编码器结果
         */
        LambdaQueryWrapper<AutoCodeResult> queryWrapper = Wrappers.<AutoCodeResult>lambdaQuery()
                .eq(AutoCodeResult::getParentCode, autoCode.getCode())
                .eq(count >= 1, AutoCodeResult::getLastInputChar, inputChar)
                .orderByDesc(AutoCodeResult::getGenerateDate);
        List<AutoCodeResult> list = autoCodeResultService.list(queryWrapper);

        AutoCodeResult autoCodeResult = list.get(0);
        autoCodeResult.setLastResult(code);
        autoCodeResult.setGenerateDate(DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss"));
        autoCodeResult.setGenerateIndex(autoCodeResult.getGenerateIndex() + 1);
        autoCodeResult.setLastSerialNumber(Integer.valueOf(lastSerialNumber));
        autoCodeResult.setLastInputChar(inputChar);
        autoCodeResultService.updateById(autoCodeResult);
    }
}
